/********************************************
 * 功能说明: 
 * 模块名称: 
 * 系统名称: java敏捷开发通用平台
 * 软件版权:
 * 系统版本: 1.0.0
 * 开发人员: zhangfb
 * 开发时间: 2018年4月30日 下午4:07:42
 * 审核人员:
 * 相关文档:
 * 修改记录: 修改日期 修改人员 修改说明
 *********************************************/
package com.hyacinth.utils.qrcode;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.Hashtable;

/**
 * 二维码工具类
 * @author zhangfb
 * @version 1.0.0.1
 */
public class QRCodeUtils {
	
	private static final Logger log = LoggerFactory.getLogger(QRCodeUtils.class);
	
	private static final int BLACK = 0xFF000000;
	private static final int WHITE = 0xFFFFFFFF;
	private static final String QRCODE_DEFULT_FORMAT = "jpg";
	private static final int QRCODE_DEFULT_WIDTH = 200;
	private static final int QRCODE_DEFULT_HEIGHT = 200;
	
	// 图片输出形式
	private enum ImgOutType {
		FILE,STREAM
	} 
	
	/**
	 * 将内容编码成二维码图片
	 * @param content     内容
	 * @param filepath    文件路径
	 */
	public static void encode(String content, String filepath) {
		// 默认不带logo
		encode(content, filepath, QRCODE_DEFULT_FORMAT, QRCODE_DEFULT_WIDTH, QRCODE_DEFULT_HEIGHT,ImgOutType.FILE, null);
	}
	
	/**
	 * 将内容编码成二维码图片
	 * @param content     内容
	 * @param filepath    文件路径
	 * @param logoPath    logo路径
	 */
	public static void encode(String content, String filepath, String logoPath) {
		encode(content, filepath, QRCODE_DEFULT_FORMAT, QRCODE_DEFULT_WIDTH, QRCODE_DEFULT_HEIGHT,ImgOutType.FILE, logoPath);
	}
	
	/**
	 * 将内容编码成二维码图片
	 * @param content     内容
	 * @param filepath    文件路径
	 * @param fileFormat  文件格式
	 * @param width       宽度
	 * @param height      高度
	 * @param logoPath    logo路径
	 */
	public static void encode(String content, 
							  String filepath,
							  String fileFormat, 
							  int width, int height,
							  ImgOutType outType,
							  String logoPath) {
		try {
			if (StringUtils.isBlank(content)) {
				throw new RuntimeException("二维码内容不能为空");
			}
			if (StringUtils.isBlank(filepath)) {
				throw new RuntimeException("文件保存路径不能为空");
			}
			if (StringUtils.isBlank(fileFormat)) {
				fileFormat = QRCODE_DEFULT_FORMAT;
			}
			// 创建工厂
			MultiFormatWriter writer = new MultiFormatWriter();
			// 组装参数
			Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); //使用hashtable  
			// 设置白边0
			hints.put(EncodeHintType.MARGIN, 0); 
			// 设置编码
			hints.put(EncodeHintType.CHARACTER_SET, "GBK");
			// 指定纠错等级(L<M<Q<H),指定纠错等级,容错率越高,二维码的有效像素点就越多.
			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
			// 编码矩阵对象
			if (width <= 0) {
				width = QRCODE_DEFULT_WIDTH;
			}
			if (height <= 0) {
				height = QRCODE_DEFULT_HEIGHT;
			}
			BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
			//删除白边
			bitMatrix = deleteWhite(bitMatrix);
			File file = new File(filepath);
			if (!file.exists()) {
				// 文件不存在则创建
				file.mkdirs();
			}
			OutputStream stream = new FileOutputStream(file);
			// 输出类型
			if (ImgOutType.FILE.equals(outType)) { 
				writeToFile(bitMatrix,fileFormat,file,logoPath);
			} else {
				writeToStream(bitMatrix, fileFormat, stream,logoPath);
			}
		} catch (Exception e) {
			log.error("生成二维码出错...", e);
		}
	}
	
	/**
	 * 根据图片路径将图片解码，返回内容
	 * @param filepath 文件路径
	 * @return
	 */
	public static String decode(String filepath) {
		String content = "";
		try {
			MultiFormatReader formatReader = new MultiFormatReader();
			File file = new File(filepath);
			if (!file.exists()) {
				throw new IOException("文件不存在");
			}
			BufferedImage image = ImageIO.read(file);
			LuminanceSource source = new BufferedImageLuminanceSource(image);
			Binarizer binarizer = new HybridBinarizer(source);
			BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
			// 解码的参数  
			Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();  
			hints.put(DecodeHintType.CHARACTER_SET, "GBK");
			Result result = formatReader.decode(binaryBitmap, hints);
			content = result.getText();
			// 输出编码信息
			log.debug("qrcode content=" + content+ "|resultFormat=" + result.getBarcodeFormat());
		} catch (Exception e) {
			log.error("解析二维码出错......", e);
		}
		return content;
	}

	/**
	 * 将二维码写入到文件中
	 * @param matrix
	 * @param format
	 * @param file
	 * @throws IOException void
	 */
	public static void writeToFile(BitMatrix matrix, String format, File file,String logoPath) throws IOException {
		try {
			BufferedImage image = toBufferedImage(matrix);
			if (StringUtils.isNotBlank(logoPath)) {
				drawLogo(image, logoPath);
			}
			if (!ImageIO.write(image, format, file)) {
				throw new IOException("Could not write an image of format " + format + " to " + file);
			}
		} catch (Exception e) {
			log.error("二维码写入到文件中出错......", e);
		}
	}

	/**
	 * 将二维码写入到流中
	 * @param matrix
	 * @param format
	 * @param stream
	 * @throws IOException
	 */
	public static void writeToStream(BitMatrix matrix, String format, OutputStream stream, String logoPath) throws IOException {
		try {
			BufferedImage image = toBufferedImage(matrix);
			if (StringUtils.isNotBlank(logoPath)) {
				drawLogo(image, logoPath);
			}
			if (!ImageIO.write(image, format, stream)) {
				throw new IOException("Could not write an image of format " + format);
			}
		} catch (Exception e) {
			log.error("二维码写入到流中出错......", e);
		}
		
	}
	
	/**
	 * 在原二维码上画logo
	 * @param source
	 * @param logoPath 
	 * @throws MalformedURLException 
	 * @throws IOException 
	 */
	private static void drawLogo(BufferedImage source, String logoPath) throws MalformedURLException {
		try {
			Graphics2D gs = source.createGraphics();
			// 载入logo
			File f = new File(logoPath);
			if (!f.exists()) {
				throw new IOException("logo不存在，logoPath|"+logoPath);
			}
			Image img = ImageIO.read(f);
			  //设置logo的大小,设置为二维码图片的20%,因为过大会盖掉二维码 
	        int widthLogo = img.getWidth(null)>source.getWidth()*2/10?(source.getWidth()*2/10):img.getWidth(null);  
	        int heightLogo = img.getHeight(null)>source.getHeight()*2/10?(source.getHeight()*2/10):img.getHeight(null);  
	        // 计算图片放置位置 ,logo放在中心 
	        int x = (source.getWidth() - widthLogo) / 2; 
	        int y = (source.getHeight() - heightLogo) / 2;
	        //开始绘制图片  
	        gs.drawImage(img, x, y, widthLogo, heightLogo, null);  
	        gs.drawRoundRect(x, y, widthLogo, heightLogo, 10, 10);  
			gs.dispose();
			img.flush();
		} catch (IOException e) {
			log.error("二维码上增加logo出错，logoPath|"+logoPath,e);
		} 
	}
	
	/**
	 * 转换成BufferedImage
	 * @param matrix
	 * @return BufferedImage
	 */
	public static BufferedImage toBufferedImage(BitMatrix matrix) {
		int width = matrix.getWidth();
		int height = matrix.getHeight();
		BufferedImage image = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
			}
		}
		return image;
	}
	
	/**
     * 删除白边
     * */
    private static BitMatrix deleteWhite(BitMatrix matrix) {
        int[] rec = matrix.getEnclosingRectangle();
        int resWidth = rec[2] + 1;
        int resHeight = rec[3] + 1;
        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
        resMatrix.clear();
        for (int i = 0; i < resWidth; i++) {
            for (int j = 0; j < resHeight; j++) {
                if (matrix.get(i + rec[0], j + rec[1]))
                    resMatrix.set(i, j);
            }
        }
        return resMatrix;
    }
    
	public static void main(String[] args) throws WriterException, IOException {
		for (int i=1;i<5;i++) {
			String content = "https://www.baidu.com";
			String filepath = "d://myQrcode"+i+".jpg";
			String logoPath = "d://logo"+i+".jpg";
			encode(content, filepath, QRCODE_DEFULT_FORMAT, QRCODE_DEFULT_WIDTH, QRCODE_DEFULT_HEIGHT,ImgOutType.FILE, logoPath);
		}
	}
}
